/*
   AppManager.m created by enoyau on Fri 13-Jan-1995

   You may freely copy, distribute, and reuse the code in this example.
   NeXT disclaims any warranty of any kind, expressed or implied, as to its
   fitness for any particular use.
*/

#import "AppManager.h"
#import "Employee.h"

// A property added to the EMPLOYEE table in the PEOPLE demo database.
// This property is needed to avoid concurrent update to the same record.
#define lockProperty @"lock"

@interface AppManager(private)
- (void)_updateDisplay;
@end

@implementation AppManager(private)

// Responsible for keeping the lock button states up-to-date and to
// allow/disallow editing in the tableview. 
- (void)_updateDisplay
{
    id currentSelection = [[eoController selectedObjects] objectAtIndex:0];

    BOOL isEditable = [currentSelection isEditable];
    BOOL isLocked = [currentSelection isLocked];

    [lockButton setTitle:isLocked ? "Unlock" : "Lock"];
    [lockButton setEnabled:(!isLocked) || isEditable];

    [tableView setTarget:isLocked ? nil : lockButton]; 
    if([tableView isEditable] != isEditable) {
        [tableView setEditable:isEditable];
        if(!isEditable) {
            [tableView perform:@selector(display)
                          with:nil
                    afterDelay:0
                cancelPrevious:NO];
        }
    }
    return;
}

@end

@implementation AppManager

// This notification is sent to AppManager by the DEONotificationCenter.
- (void)databaseChange:(DEONotification *)notification
{
    id eoObject = [notification notificationObject];

    if( !([EOFault isFault:eoObject] || [notification isLocal]) ) {
        EODatabaseDataSource *eoDatabasedataSource;
        EODatabaseChannel *eoDatabaseChannel;
        EODatabaseContext *eoDatabaseContext;
        BOOL autoTransaction = NO;

        eoDatabasedataSource =(EODatabaseDataSource*)[eoController dataSource];
        eoDatabaseChannel = [eoDatabasedataSource databaseChannel];
        eoDatabaseContext = [eoDatabaseChannel databaseContext];

        // refetch the EO from the database server to preserve data integrity.
        if (![eoDatabaseContext transactionNestingLevel]) {
            [eoDatabaseContext beginTransaction];
            autoTransaction = YES;
        }

        [eoDatabaseChannel refetchObject:eoObject];

        if (autoTransaction) [eoDatabaseContext commitTransaction];

        // redisplay the data. If editing is not ended, wait until it is done
        // (by changing the selection or the state of the lock)       
        if([tableView isEditable]) {
            needRedisplay = YES;
        } else {
            [eoController redisplay];
            [self _updateDisplay];
        }
    }
    return;
}

// Update the lock value in the database when lock button is pressed.
- toggleLockOnSelection:sender
{
    if([lockButton isEnabled]) {
        id currentSelection;
        NSArray *keys;
        NSMutableDictionary *objectValue;

        currentSelection = [[eoController selectedObjects] objectAtIndex:0];
        keys = [NSArray arrayWithObject:lockProperty];
        objectValue = [[[currentSelection valuesForKeys:keys] mutableCopy] autorelease];

        if([[objectValue objectForKey:lockProperty] isEqual:[EONull null]]) {
            [objectValue setObject:lockString forKey:lockProperty];
        } else {
            [objectValue setObject:[EONull null] forKey:lockProperty];
        }

        [eoController setValues:objectValue forObject:currentSelection];

        [self _updateDisplay];
    }
    return self;
}
@end

@implementation AppManager(EOControllerDelegate)
- (void)controllerDidChangeSelection:(EOController *)controller
{
    if(needRedisplay) {
        [eoController redisplay];
        needRedisplay = NO;
    }
    [self _updateDisplay];
}
@end

@implementation AppManager(EOApplicationDelegate)
- appDidInit:sender
{
    EODatabaseDataSource *eoDatabaseDataSource;
    EODatabaseChannel *eoDatabaseChannel;
    EOAdaptorContext *eoAdaptorContext;

    eoDatabaseDataSource = [eoController dataSource];
    eoDatabaseChannel = [eoDatabaseDataSource databaseChannel];
    eoAdaptorContext = [[eoDatabaseChannel adaptorChannel] adaptorContext];
    
    // Set up as the delegate of EOController, EODatabaseChannel,
    // and EOAdaptorContext
    [(EOController*)eoController setDelegate:self];
    [eoDatabaseChannel setDelegate:self]; // EO updates, inserts and deletes.
    [eoAdaptorContext setDelegate:self];  // Transactions begin, commit or rollback.

    // Put an ImageFormatter in column 0
    [[tableView columnAt:0] setFormatter:[[NXImageFormatter alloc] init]];

    // A double click will lock the current selection to start the
    // editing mode
    [tableView setTarget:lockButton];
    [tableView setDoubleAction:@selector(performClick:)];
    
    // Set up the Distributed Entreprise Object notification center
    deoNotificationCenter =
        [[DEONotificationCenter alloc]
                     initWithDatabaseChannel:eoDatabaseChannel
                                      entity:[eoDatabaseDataSource entity]];

    // Register for notification whenever objects of this entity are updated
    [deoNotificationCenter addObserver:self
                              selector:@selector(databaseChange:)
                      notificationName:DEOUpdate
                                object:nil];

    // Update instance variables
    transactions = [NSMutableArray new];
    needRedisplay = NO;
    lockString = [[NSString alloc] initWithFormat:@"%s-%@-%d",
        NXUserName(),
        [DEONotificationCenter localHostname],
        NXProcessID];
    
    [Employee setLocalLockString:lockString];
    
    // Force the first fetch
    [eoController fetch:self];

    [self _updateDisplay];
    return self;
}

- appWillTerminate:sender
{
    [transactions release];
    [lockString release];
    [deoNotificationCenter removeObserver:self];
    [deoNotificationCenter release];
    return self;
}
@end

// We keep a list of all objects updated in the current transaction
// and post notification of the changes when the transaction commits.
// A more complete implementation could also keep track of insertions
// and deletions.
@implementation AppManager(EOAdaptorContextDelegate)
- (void)adaptorContextDidBegin:context
{
    [transactions addObject:[NSMutableArray array]];
}

- (void)adaptorContextDidCommit:context
{
    NSMutableDictionary *commitedTransaction = [transactions lastObject];
    id enumerator, current;

    [transactions removeLastObject];
    if([transactions count]) {
        [[transactions lastObject] addObjectsFromArray:commitedTransaction];
    } else {
        enumerator = [commitedTransaction objectEnumerator];
        while(current = [enumerator nextObject]) {
            [deoNotificationCenter postNotificationName:DEOUpdate object:current];
        }
    }
}

// If the transaction rolls back, there is no notification.
- (void)adaptorContextDidRollback:context
{
    [transactions removeLastObject];
}

@end

@implementation AppManager(EODatabaseChannelDelegate)
- (void)databaseChannel:channel didUpdateObject:anEO
{
    [(NSMutableArray *)[transactions lastObject] addObject:anEO];
}
@end
